﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace Titan.Reflection
{
    internal static class ObjectConstructor
    {
        private interface IObjectConstructor
        {
            object Create(object[] args);
        }

        public static object Create(Type type, object[] args)
        {
            IObjectConstructor ctor = GetConstructor(type, args);
            if (null != ctor)
                return ctor.Create(args);
            else
            {
                return type.InvokeMember(null, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance, null, null, args);
            }
        }

        private static IObjectConstructor GetConstructor(Type type, object[] args)
        {
            IObjectConstructor ctor;
            if (null == args || 0 == args.Length)
            {
                bool contains = _methodMap.TryGetValue(type, out ctor);
                if (!contains)
                {
                    lock (_methodMap)
                    {
                        contains = _methodMap.TryGetValue(type, out ctor);
                        if (!contains)
                        {
                            ConstructorInfo ctorInfo = Binder.Default.SelectConstructor(type, BindingFlags.Instance | BindingFlags.Public, null, null);
                            if (null != ctorInfo)
                            {
                                Type objCtorType = typeof(PublicNonParameterObjectFactory<>).MakeGenericType(type);
                                ctor = (IObjectConstructor)Activator.CreateInstance(objCtorType);
                            }
                            else
                                ctor = null;

                            _methodMap.Add(type, ctor);
                        }
                    }
                }
                return ctor;
            }
            return null;
        }

        private static Dictionary<Type, IObjectConstructor> _methodMap = new Dictionary<Type, IObjectConstructor>();
        private static object _lock = new object();

        private class PublicNonParameterObjectFactory<T> : IObjectConstructor where T : new()
        {
            public object Create(object[] args)
            {
                return new T();
            }

            public PublicNonParameterObjectFactory() { }
        }
    }
}
